/*
 * Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.registry.core;

import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.dao.ResourceDAO;
import org.wso2.carbon.registry.core.dataaccess.DataAccessManager;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.dataobjects.ResourceDO;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.user.core.UserRealm;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * Represents any file or collection stored in the registry. It encapsulates both the content of the
 * entity and its meta data. In addition to files and collections, instances of Resource are used to
 * represent results of runtime queries. In such scenarios, comments, tags, ratings as well as
 * collections of comments, etc. are also represented by Resource objects.
 * <p/>
 * Each resource instance contains a unique path within a Registry instance. {@link
 * Registry#get(String)} method invocation using this path gives an instance of that resource. This
 * path can be combined with the base URL of the registry server to generate a URI for the
 * resource.
 * <p/>
 * <strong>Handling content</strong>
 * <p/>
 * Contents of resource can be set either as an input stream or an object. If an input stream is
 * set, an unique file based content will be created for the given input stream. If an object of
 * type byte[] or String is set, an in-memory input stream will be created from it and then a
 * unique file based input stream is created. If an object of any other type is set, there should be
 * a handler to convert it to an input stream before reaching the repository.
 * <p/>
 * When a resource is retrieved from the database layer, its content is not retrieved from the
 * database. Instead, UUID referring to the database content is stored in dbBasedContentID. When the
 * content is first accessed, a file based input stream is created from the database content and
 * fileBasedContentID is set (fileBasedContentID = dbBasedContentID).
 */
public class ResourceImpl implements Resource {

    /**
     * UUID to identify the resource. currently this is same as the path
     */
    protected String id;

    /**
     * Snapshot for which this resource instance is created. Any resource and all its version-ed
     * data can be accessed only using the resource ID and the version number. But snapshot ID is
     * required to track currently accessing snapshot. Note that it is not possible to derive
     * snapshot ID from the resource ID and the version number, as there can be multiple snapshots
     * using the same version.
     */
    protected long snapshotID;

    /**
     * Version of this instance of the resource. Version-ed attributes (e.g. content, last modified
     * date) can be accessed by using the combination of id and versionNumber.
     */
    protected long versionNumber;

    /**
     * User name of the user who added the resource to the registry.
     */
    protected String authorUserName;

    /**
     * Time at which the resource is first added to the registry.
     */
    protected long createdTime;

    /**
     * User name of the user who modified the resource most recently.
     */
    protected String lastUpdaterUserName;

    /**
     * Time at which the resource modified most recently.
     */
    protected long lastModified;

    /**
     * Description about the resource. This may contain any text including HTML fragments.
     */
    protected String description;

    /**
     * Unique path of the resource within the registry. This is generated by appending all ascendant
     * paths to the resource name separated by "/". For example assume that the resource name is
     * "users.xml". config.xml if inside the collection named "config". config collection is inside
     * the root level collection named "servers". Then the path of the resource is
     * /servers/config/users.xml.
     */
    protected String path;

    /**
     * The matching snapshot identifier.
     */
    protected long matchingSnapshotID;

    /**
     * The permanent path of the resource. This is a path that will not change for the given
     * resource's lifetime.
     */
    protected String permanentPath;

    /**
     * Media type of the resource. Each resource can have a media type associated with it. This can
     * be either a standard MIME media type or a custom media type defined by the users of the
     * registry. Media type is used to activate media type handlers defined in the registry. Thus,
     * by defining a media type for a resource and by registering a media type handler to handle
     * that media type, it is possible to apply special processing for resources.
     */
    protected String mediaType;

    /**
     * Path of the parent collection of the resource. If the resource path is
     * /servers/config/users.xml, parent path is /servers/config.
     */
    protected String parentPath;

    /**
     * Used to detect whether the resource content is modified after it is retrieved from the
     * Registry. If this is set to true at the time of adding the resource back to the Registry, new
     * version will be created.
     */
    protected boolean contentModified;

    /**
     * Used to detect whether properties of this resource is modified since retrieval from the
     * repository. If true, properties will be updated and new version will be created upon putting
     * this resource to the repository.
     */
    protected boolean propertiesModified;

    /**
     * Determines whether the resource is subjected to changes, which causes it to create a new
     * version. If true, a new version will be created upon adding the resource to the repository.
     */
    protected boolean versionableChange;

    /**
     * Normal resources have the state RegistryConstants.ACTIVE_STATE (100) Deleted resources have
     * the state RegistryConstants.DELETED_STATE (101)
     */
    protected int state;

    /**
     * Properties associated with the resource. A resource can contain zero or more properties,
     * where each property is a name->value pair. Both name and the value should be strings.
     */
    protected Properties properties = new Properties();

    /**
     * UUID of the content stored in the database. This ID is set when a resource is retrieved from
     * the database. Upon the first getContentStream() call, a file based content will be created
     * from this database based content and file based content ID will be set.
     */
    protected int dbBasedContentID = -1;

    /**
     * Content of the resource. Object and the type stored in this field depends on the resource
     * type. If the resource is a file with no special media type handling, this contains an array
     * of bytes (byte[]) containing the raw bytes of the file. If the resource is a collection, this
     * contains a String[] containing the paths of child resources. If the resource is processed by
     * a media type handler, it is up to the media type handler to set a content. In that case
     * content can be anything ranging from String to custom type. Therefore, clients of the API
     * should be aware of the media type and the content for that media type.
     */
    protected Object content;

    /**
     * Specified whether the resource is a collection (directory) or a file. By default all
     * resources are considered as files. If you want to make a collection, you have to explicitly
     * set the directory field to true.
     */
    protected boolean directory = false;

    /**
     * Content of the resource, represented as a input stream.
     */
    //protected InputStream contentStream;


    /**
     * The data access manager is to be used only by the resource implementation and users of the
     * resource are not needed to use this. Some attributes of the resource (e.g. content) is
     * fetched upon the first request to optimize the response time. Getters of such attributes use
     * this data access manager to fetch the values from the database.
     */
    protected DataAccessManager dataAccessManager;

    /**
     * Resources and collections can be accessed outside the user registry. There are some
     * operations in these entities that require fetching fresh data, that are not authorized
     * previously. For authorizing those data, it is necessary to store the user name and the realm
     * of the user who retrieved the resource/collection instance.
     */
    protected String userName;

    /**
     * Resources and collections can be accessed outside the user registry. There are some
     * operations in these entities that require fetching fresh data, that are not authorized
     * previously. For authorizing those data, it is necessary to store the user name and the realm
     * of the user who retrieved the resource/collection instance.
     */
    protected UserRealm userRealm;

    /**
     * tenant id resource belongs to
     */
    protected int tenantId;

    /**
     * Resource DAO instance to access the database directly. Note that the Version-ed ResourceDAO
     * is thread safe, so it is possible to keep a single instance for the resource.
     */
    protected ResourceDAO resourceDAO;

    /**
     * path id, if collection - id of the path of the collection if non-collection - id of the path
     * of the non-collection
     */
    protected int pathID;

    /**
     * the name of the resource if collection - NULL non-collection - the name of the resource..
     */
    protected String name;

    /*
     * Defines the UUID of the resource
     */
    protected String uuid;


    /**
     * Default constructor for the resource. Creates an empty resource.
     */

    public ResourceImpl() {
        if (RegistryContext.getBaseInstance() != null) {
            this.dataAccessManager = RegistryContext.getBaseInstance().getDataAccessManager();
        } else {
            this.dataAccessManager = null;
        }
        if (dataAccessManager != null) {
            this.resourceDAO = dataAccessManager.getDAOManager().getResourceDAO();
        }
        this.id = null;
        this.snapshotID = -1;
        this.matchingSnapshotID = -1;
    }

    /**
     * Construct a resource with given path and resource data object.
     *
     * @param path       the path of the resource.
     * @param resourceDO the resource data object.
     */
    public ResourceImpl(String path, ResourceDO resourceDO) {
        this.id = path;
        this.snapshotID = -1;
        this.versionNumber = resourceDO.getVersion();
        this.authorUserName = resourceDO.getAuthor();
        this.createdTime = resourceDO.getCreatedOn();
        this.lastUpdaterUserName = resourceDO.getLastUpdater();
        this.lastModified = resourceDO.getLastUpdatedOn();
        this.description = resourceDO.getDescription();
        this.path = path;
        this.matchingSnapshotID = -1;
        this.mediaType = resourceDO.getMediaType();
        this.parentPath = RegistryUtils.getParentPath(path);
        this.contentModified = true;
        this.propertiesModified = true;
        this.versionableChange = true;
        this.state = -1;
        this.dbBasedContentID = resourceDO.getContentID();
        this.content = null;
        if (RegistryContext.getBaseInstance() != null) {
            this.dataAccessManager = RegistryContext.getBaseInstance().getDataAccessManager();
        } else {
            this.dataAccessManager = null;
        }
        if (dataAccessManager != null) {
            this.resourceDAO = dataAccessManager.getDAOManager().getResourceDAO();
        }
        this.userName = null;
        this.userRealm = null;
        this.pathID = resourceDO.getPathID();
        this.name = resourceDO.getName();
        this.uuid = resourceDO.getUUID();
    }

    /**
     * A copy constructor used to create a shallow-copy of this resource.
     *
     * @param resource the resource of which the copy is created.
     */
    public ResourceImpl(ResourceImpl resource) {
        this.id = resource.id;
        this.snapshotID = resource.snapshotID;
        this.versionNumber = resource.versionNumber;
        this.authorUserName = resource.authorUserName;
        this.createdTime = resource.createdTime;
        this.lastUpdaterUserName = resource.lastUpdaterUserName;
        this.lastModified = resource.lastModified;
        this.description = resource.description;
        this.path = resource.path;
        this.matchingSnapshotID = resource.matchingSnapshotID;
        this.permanentPath = resource.permanentPath;
        this.mediaType = resource.mediaType;
        this.parentPath = resource.parentPath;
        this.contentModified = resource.contentModified;
        this.propertiesModified = resource.propertiesModified;
        this.versionableChange = resource.versionableChange;
        this.state = resource.state;
        this.directory = resource.directory;
        this.userName = resource.userName;
        this.tenantId = resource.tenantId;
        this.pathID = resource.pathID;
        this.name = resource.name;
        this.dbBasedContentID = resource.dbBasedContentID;
        this.properties.putAll(resource.properties);

        this.content = resource.content;
        this.dataAccessManager = resource.dataAccessManager;
        this.userRealm = resource.userRealm;
        this.resourceDAO = resource.resourceDAO;
        this.uuid = resource.getUUID();
    }

    /**
     * Method to get the UUID
     *
     * @return the UUID of the resource
     */
    public String getUUID() {
        return uuid;
    }

    /**
     * Method to set the UUID of the resource
     *
     * @param uuid the UUID of the resource
     */
    public void setUUID(String uuid) {
        this.uuid = uuid;
    }

    /**
     * Method to set the data access manager.
     *
     * @param dataAccessManager the data access manager.
     */
    public void setDataAccessManager(DataAccessManager dataAccessManager) {
        this.dataAccessManager = dataAccessManager;
        if (dataAccessManager != null) {
            this.resourceDAO = dataAccessManager.getDAOManager().getResourceDAO();
        }
    }

    /**
     * Method to set the user name.
     *
     * @param userName the user name.
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * Method to set the user realm.
     *
     * @param userRealm the user realm.
     */
    public void setUserRealm(UserRealm userRealm) {
        this.userRealm = userRealm;
    }

    /**
     * Method to set the tenant id associated with the resource.
     *
     * @param tenantId the tenant id.
     */
    public void setTenantId(int tenantId) {
        this.tenantId = tenantId;
    }

    /**
     * The Resource ID, In the default implementation this returns the path.
     *
     * @return the resource id
     */
    public String getId() {
        return path;
    }

    /**
     * Method to set the resource id, currently it will just set the path.
     *
     * @param id the path
     */
    public void setId(String id) {
        this.path = id;
    }

    /**
     * Method to get the snapshot id.
     *
     * @return the snapshot id.
     */
    @SuppressWarnings("unused")
    public long getSnapshotID() {
        return snapshotID;
    }

    /**
     * Method to set the snapshot id.
     *
     * @param snapshotID the snapshot id.
     */
    public void setSnapshotID(long snapshotID) {
        this.snapshotID = snapshotID;
    }

    /**
     * Method to get the version number.
     *
     * @return the version number.
     */
    public long getVersionNumber() {
        return versionNumber;
    }

    /**
     * Method to set the version number.
     *
     * @param versionNumber the version number.
     */
    public void setVersionNumber(long versionNumber) {
        this.versionNumber = versionNumber;
    }

    /**
     * Method to get the author user name.
     *
     * @return the author user name.
     */
    public String getAuthorUserName() {
        return authorUserName;
    }

    /**
     * Method to set the author user name.
     *
     * @param authorUserName the author user name.
     */
    public void setAuthorUserName(String authorUserName) {
        this.authorUserName = authorUserName;
    }

    /**
     * Method to get the created time.
     *
     * @return the created time.
     */
    public Date getCreatedTime() {
        return new Date(createdTime);
    }

    /**
     * Method to set the created time.
     *
     * @param createdTime the created time.
     */
    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime.getTime();
    }

    /**
     * Method to get the created time.
     *
     * @return the created time
     */
    public Date getLastModified() {
        return new Date(lastModified);
    }

    /**
     * Method to set the last modified date.
     *
     * @param lastModified the last modified date.
     */
    public void setLastModified(Date lastModified) {
        this.lastModified = lastModified.getTime();
    }

    /**
     * Method to get the description.
     *
     * @return the description.
     */
    public String getDescription() {
        return description;
    }

    /**
     * Method to set the description.
     *
     * @param description the description.
     */
    public void setDescription(String description) {
        this.description = description;
        versionableChange = true;
    }

    /**
     * Method to get the path. the unique identifier of the resources in the present state.
     *
     * @return the path.
     */
    public String getPath() {
        return path;
    }

    /**
     * Method to get the path. the unique identifier of the resources in the present state.
     *
     * @param path the path.
     */
    public void setPath(String path) {
        this.path = path == null ? null : preparePath(path);
    }

    /**
     * Method to get the matching snapshot id.
     *
     * @return the snapshot id.
     */
    public long getMatchingSnapshotID() {
        return matchingSnapshotID;
    }

    /**
     * Method to set the matching snapshot id.
     *
     * @param matchingSnapshotID the snapshot id.
     */
    public void setMatchingSnapshotID(long matchingSnapshotID) {
        this.matchingSnapshotID = matchingSnapshotID;

        if (matchingSnapshotID == -1) {
            permanentPath = null;
        } else {
            permanentPath =
                    path + RegistryConstants.URL_SEPARATOR + "version:" + matchingSnapshotID;
        }
    }

    /**
     * Method to get the permanent path.
     *
     * @return the permanent path.
     */
    public String getPermanentPath() {
        return permanentPath;
    }

    /**
     * Method to get the media type.
     *
     * @return the media type.
     */
    public String getMediaType() {
        return mediaType;
    }

    /**
     * Method to set the media type.
     *
     * @param mediaType the media type.
     */
    public void setMediaType(String mediaType) {
        if (mediaType != null) {
            mediaType = (mediaType.trim().equals("")) ? null : mediaType;
        }
        this.mediaType = mediaType;
        versionableChange = true;
    }

    /**
     * Method to get the parent path.
     *
     * @return the parent path.
     */
    public String getParentPath() {
        if (parentPath != null) {
            return parentPath;
        }
        if ((path == null) || path.length() == 1) {
            return null;
        }
        int i = path.lastIndexOf('/');
        return path.substring(0, i);
    }

    /**
     * Method to set the parent path.
     *
     * @param parentPath the parent path.
     */
    public void setParentPath(String parentPath) {
        this.parentPath = parentPath;
    }

    /**
     * Method to get the state.
     *
     * @return the state.
     */
    public int getState() {
        return state;
    }

    /**
     * Method to set the state.
     *
     * @param state the state.
     */
    public void setState(int state) {
        this.state = state;
    }

    /**
     * Get the property value for the given key, if there are multiple value for that key, it will
     * return the first value.
     *
     * @param key the property key.
     *
     * @return the property value.
     */
    public String getProperty(String key) {

        List<String> propValues = getPropertyValues(key);
        if (propValues == null) {
            return null;
        } else {
            return propValues.get(0);
        }
    }

    /**
     * Returns the list of values for the given property name. Note that these values are read-only.
     * Changes made to these values will not be persisted on putting the resource.
     *
     * @param key Key of the property.
     *
     * @return List of values of the given property key.
     */
    @SuppressWarnings("unchecked")
    // We are certain that we have a list of strings in this case.
    public List<String> getPropertyValues(String key) {
        return (List<String>) properties.get(key);
    }

    /**
     * Returns all properties of the resource. Properties are stored as key (String) -> values
     * (List) pairs. It is not recommended to use this method to access properties. Instead, use
     * other property related Resource API methods provided.
     * <p/>
     * Note that these values are read-only. Changes made to these values will not be persisted on
     * putting the resource.
     *
     * @return All properties of the resource.
     */
    public Properties getProperties() {

        return properties;
    }

    /**
     * Remove property.
     *
     * @param key the property key.
     */
    public void removeProperty(String key) {

        removePropertyWithNoUpdate(key);
        setPropertiesModified(true);
    }


    /**
     * Remove property without modifiying the resource.
     *
     * @param key the property key.
     */
    public void removePropertyWithNoUpdate(String key) {
        if (key != null) {
            properties.remove(key);
        }
    }

    /**
     * Remove property value.
     *
     * @param key   the property key.
     * @param value the property value.
     */
    public void removePropertyValue(String key, String value) {

        List<String> propValues = getPropertyValues(key);

        if (propValues != null) {
            propValues.remove(value);
            setPropertiesModified(true);
        }
    }

    /**
     * Edit property value.
     *
     * @param key      the key.
     * @param oldValue the old value.
     * @param newValue the new value.
     */
    public void editPropertyValue(String key, String oldValue, String newValue) {

        List<String> propValues = getPropertyValues(key);

        if (propValues != null) {
            propValues.remove(oldValue);
            propValues.add(newValue);
            setPropertiesModified(true);
        }
    }

    /**
     * Set a property with single value.
     *
     * @param key   the property key.
     * @param value the property value.
     */
    public void setProperty(String key, String value) {
        List<String> propValues = new ArrayList<String>();
        propValues.add(value);
        properties.put(key, propValues);
    }

    /**
     * Set a property with multiple value.
     *
     * @param key   the property key.
     * @param value the property values.
     */
    public void setProperty(String key, List<String> value) {
        setPropertyWithNoUpdate(key, value);

        setPropertiesModified(true);
    }

    /**
     * Set a property with multiple value.
     *
     * @param key   the property key.
     * @param value the property values.
     */
    private void setPropertyWithNoUpdate(String key, List<String> value) {
        properties.put(key, value);
    }

    /**
     * Add a property value for the provided key. If there are values associated with the key, this
     * will add append value. If not this will create a new property value for the key.
     *
     * @param key   the property key.
     * @param value the property value.
     */
    public void addProperty(String key, String value) {
        addPropertyWithNoUpdate(key, value);

        setPropertiesModified(true);
    }

    /**
     * Add a property value for the provided key. If there are values associated with the key, this
     * will add append value. If not this will create a new property value for the key. Here the
     * resource modified flag is not set.
     *
     * @param key   the property key.
     * @param value the property value.
     */
    public void addPropertyWithNoUpdate(String key, String value) {
        List<String> propValues = getPropertyValues(key);
        if (propValues != null) {
            // no need to check if the value is already there. we should permit adding repeating
            // values
            propValues.add(value);
        } else {
            propValues = new ArrayList<String>();
            propValues.add(value);
            setPropertyWithNoUpdate(key, propValues);
        }
    }

    /**
     * Set properties.
     *
     * @param properties the properties.
     */
    public void setProperties(Properties properties) {
        if (properties != null) {
            this.properties = properties;
        }

        setPropertiesModified(true);
    }

    /**
     * Method to get the db id of the content
     *
     * @return the db content id.
     */
    public int getDbBasedContentID() {
        return dbBasedContentID;
    }

    /**
     * Method to set the db id of the content.
     *
     * @param dbBasedContentID the db id of the content.
     */
    public void setDbBasedContentID(int dbBasedContentID) {
        this.dbBasedContentID = dbBasedContentID;
    }

    /**
     * Method to get the content stream.
     *
     * @return the content stream.
     * @throws RegistryException throws if the operation fail.
     */
    public InputStream getContentStream() throws RegistryException {

        if (content == null) {
            throw new RegistryException("Resource content is empty.");
        }

        if (content instanceof byte[]) {
            return new ByteArrayInputStream((byte[]) content);

        } else if (content instanceof String) {
            byte[] contentBytes = RegistryUtils.encodeString(((String) content));
            return new ByteArrayInputStream(contentBytes);

        } else if (content instanceof InputStream) {
            return (InputStream) content;

        } else {
            throw new RegistryException(
                    "Cannot return input stream for content of type: " +
                            content.getClass().getName());
        }
    }

    /**
     * Invalidates the current file based content and creates a new file based content for the new
     * content stream. Given content stream will be closed after completing this method.
     *
     * @param contentStream input stream containing the new content
     *
     * @throws RegistryException throws if the operation fail.
     */
    public void setContentStream(InputStream contentStream) throws RegistryException {

        setContentStreamWithNoUpdate(contentStream);

        setContentModified(true);
    }

    /**
     * Invalidates the current file based content and creates a new file based content for the new
     * content stream. Given content stream will be closed after completing this method. Here the
     * resource modified flag is not set.
     *
     * @param contentStream input stream containing the new content
     *
     * @throws RegistryException throws if the operation fail.
     */
    public void setContentStreamWithNoUpdate(InputStream contentStream) throws RegistryException {

        content = RegistryUtils.getByteArray(contentStream);
    }

    /**
     * Method to get the content of the resource. If the resource is a collection this will return
     * an array of string that represent the paths of its children, otherwise it returns an byte
     * array or a string from the default resource implementation.
     *
     * @return the content.
     * @throws RegistryException throws if the operation fail.
     */
    public Object getContent() throws RegistryException {
        return content;
    }

    /**
     * Set the content of the resource.
     *
     * @param content the resource.
     *
     * @throws RegistryException throws if the operation fail.
     */
    public void setContent(Object content) throws RegistryException {

        setContentWithNoUpdate(content);

        setContentModified(true);
    }

    /**
     * Set the content of the resource without setting the modified flag.
     *
     * @param content the resource.
     *
     * @throws RegistryException throws if the operation fail.
     */
    public void setContentWithNoUpdate(Object content) throws RegistryException {

        this.content = content;
    }

    /**
     * Prepare the resource content to be put.
     *
     * @throws RegistryException throws if the operation fail.
     */
    public void prepareContentForPut() throws RegistryException {

        if (content instanceof String) {
            content = RegistryUtils.encodeString((String) content);
        } else if (content instanceof InputStream) {
            content = RegistryUtils.getByteArray((InputStream) content);
        }
    }

    /**
     * Format the path to the standard way.
     *
     * @param path the path.
     *
     * @return the formatted path.
     */
    private String preparePath(String path) {

        String preparedPath = path;

        // make sure that the path does not end with a "/"
        if (!path.equals(RegistryConstants.ROOT_PATH) &&
                path.endsWith(RegistryConstants.PATH_SEPARATOR)) {
            preparedPath =
                    path.substring(0, path.length() - RegistryConstants.PATH_SEPARATOR.length());
        }

        return preparedPath;
    }

    /**
     * Method to get the last updated user name.
     *
     * @return the last updated user name.
     */
    public String getLastUpdaterUserName() {
        return lastUpdaterUserName;
    }

    /**
     * Method to set the last updater user name.
     *
     * @param lastUpdaterUserName the last updater user name.
     */
    public void setLastUpdaterUserName(String lastUpdaterUserName) {
        this.lastUpdaterUserName = lastUpdaterUserName;
    }

    /**
     * Method to check if the content is modified.
     *
     * @return true, if it is modified, false otherwise.
     */
    public boolean isContentModified() {
        return contentModified;
    }

    /**
     * This method is used to explicitly set the content modified state of the resource. Normal
     * users of the Registry API should not call this method.
     *
     * @param contentModified true if we want to add a new version upon putting this to the
     *                        registry. false otherwise.
     */
    public void setContentModified(boolean contentModified) {
        this.contentModified = contentModified;
        setLastModified(new Date());
        versionableChange = true;
    }

    /**
     * Method to check whether the properties are modified.
     *
     * @return true if the properties modified, false otherwise.
     */
    @SuppressWarnings("unused")
    public boolean isPropertiesModified() {
        return propertiesModified;
    }

    /**
     * Method to set the whether properties are modified.
     *
     * @param propertiesModified whether the properties modified or not.
     */
    public void setPropertiesModified(boolean propertiesModified) {
        this.propertiesModified = propertiesModified;
        setLastModified(new Date());
        versionableChange = true;
    }

    /**
     * Make the properties modified flag true, without changing the last updated time.
     *
     * @param propertiesModified whether the properties modified or not.
     */
    @SuppressWarnings("unused")
    public void setPropertiesModifiedWithNoUpdate(boolean propertiesModified) {
        this.propertiesModified = propertiesModified;
    }

    /**
     * Check whether there are any changes that need to make a version
     *
     * @return true, if there are version-able changes, false otherwise.
     */
    public boolean isVersionableChange() {
        return versionableChange;
    }

    /**
     * Method to set whether there are any changes that need to make a version
     *
     * @param versionableChange whether version-able change is made or not.
     */
    public void setVersionableChange(boolean versionableChange) {
        setLastModified(new Date());
        this.versionableChange = versionableChange;
    }

    /**
     * Get the aspects associated with the resource.
     *
     * @return an array of associated aspects.
     */
    public List<String> getAspects() {
        return getPropertyValues(Aspect.AVAILABLE_ASPECTS);
    }

    /**
     * Method to add an aspect.
     *
     * @param name the name of the aspect.
     */
    public void addAspect(String name) {
        List<String> aspects = getPropertyValues(Aspect.AVAILABLE_ASPECTS);
        if (aspects == null) {
            aspects = new ArrayList<String>();
        }
        aspects.add(name);
        setProperty(Aspect.AVAILABLE_ASPECTS, aspects);
    }

    /**
     * Method to remove an aspect.
     *
     * @param name the name of the aspect to remove.
     */
    public void removeAspect(String name) {
        List<String> aspects = getPropertyValues(Aspect.AVAILABLE_ASPECTS);
        if (aspects != null) {
            aspects.remove(name);
        }
    }

    /**
     * Method to discard the resource
     */
    public void discard() {

    }

    /**
     * Set the session information.
     */
    protected void setSessionInformation() {
        CurrentSession.setUser(userName);
        CurrentSession.setUserRealm(userRealm);
        CurrentSession.setTenantId(tenantId);
    }

    /**
     * UnSet the session information.
     */
    protected void clearSessionInformation() {
        CurrentSession.removeUser();
        CurrentSession.removeUserRealm();
        CurrentSession.removeTenantId();
    }

    /**
     * Get the path id.
     *
     * @return the path id.
     */
    public int getPathID() {
        return pathID;
    }

    /**
     * Set path id.
     *
     * @param pathID the path id.
     */
    public void setPathID(int pathID) {
        this.pathID = pathID;
    }

    /**
     * Get the resource name.
     *
     * @return the resource name.
     */
    public String getName() {
        return name;
    }

    /**
     * Method to set the name.
     *
     * @param name the name.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Get the resource id implementation instance.
     *
     * @return the resource id.
     */
    public ResourceIDImpl getResourceIDImpl() {
        ResourceIDImpl resourceIDImpl = new ResourceIDImpl();
        resourceIDImpl.setCollection(this instanceof CollectionImpl);
        resourceIDImpl.setPathID(pathID);
        if (name == null && !(this instanceof CollectionImpl)) {
            name = RegistryUtils.getResourceName(path);
        }
        resourceIDImpl.setName(name);
        resourceIDImpl.setPath(path);

        return resourceIDImpl;
    }

    /**
     * Method to get the resource data object.
     *
     * @return the resource data object.
     */
    public ResourceDO getResourceDO() {
        ResourceDO resourceDO = new ResourceDO();
        resourceDO.setPathID(this.getPathID());
        resourceDO.setName(this.name);
        resourceDO.setVersion(this.versionNumber);
        resourceDO.setMediaType(this.mediaType);
        resourceDO.setAuthor(this.authorUserName);
        resourceDO.setCreatedOn(this.createdTime);
        resourceDO.setLastUpdater(this.lastUpdaterUserName);
        resourceDO.setLastUpdatedOn(this.lastModified);
        resourceDO.setDescription(this.description);
        resourceDO.setContentID(this.dbBasedContentID);
        resourceDO.setUUID(this.uuid);
        return resourceDO;
    }


    /**
     * Create a shallow copy of the resource.
     *
     * @return the new resource with a shallow copy of the current resource.
     * @throws RegistryException throws if the operation fail.
     */
    public ResourceImpl getShallowCopy() throws RegistryException {
        ResourceImpl newResource = new ResourceImpl();
        fillResourceCopy(newResource);

        return newResource;
    }

    /**
     * Fill the resource.
     *
     * @param resource the resource to be filled.
     *
     * @return the filled resource
     * @throws RegistryException throws if the operation fail.
     */
    protected ResourceImpl fillResourceCopy(ResourceImpl resource) throws RegistryException {
        resource.setId(this.id);
        resource.setSnapshotID(this.snapshotID);
        resource.setVersionNumber(this.versionNumber);
        resource.setAuthorUserName(this.authorUserName);
        resource.setCreatedTime(new Date(this.createdTime));
        resource.setDescription(this.description);
        resource.setPath(this.path);
        resource.setMatchingSnapshotID(this.matchingSnapshotID);
        resource.setMediaType(this.mediaType);
        resource.setParentPath(this.parentPath);
        resource.setContentModified(this.contentModified);
        resource.setPropertiesModified(this.propertiesModified);
        resource.setVersionableChange(this.versionableChange);
        resource.setState(this.state);
        resource.setProperties(this.properties);
        resource.setDbBasedContentID(dbBasedContentID);
        if (this.content != null) {
            resource.setContent(this.content);
        }
        resource.setDataAccessManager(this.dataAccessManager);
        resource.setUserName(this.userName);
        resource.setTenantId(this.tenantId);
        resource.setUserRealm(this.userRealm);
        resource.setPathID(this.pathID);
        resource.setName(this.name);
        resource.setUUID(this.uuid);

        // having these two fields at the end is a must, as above field have an affect of them
        resource.setLastUpdaterUserName(this.lastUpdaterUserName);
        resource.setLastModified(new Date(this.lastModified));

        return resource;
    }
}
